5. Inflation#
Inflation is the rate at which the general level of prices for goods and sevices changes.
Show code cell source
# Load libraries
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import requests
from io import BytesIO
Show code cell source
# Import data
folder = 'https://raw.githubusercontent.com/Basics2022/bbooks-financial-edu/master/code/data/'
#> Files
# FOI-prices: 2016-.../2025-...
# IPCA-weights: 2018/2025
# IPCA-prices: 2018-.../2025-...
# NIC-weights: 2018/2025
# NIC-prices: 2018-.../2025-...
filen = {
'FOI-prices' : folder+'monthly-FOI.xlsx', # folder+'monthly-FOI.xlsx' ,
'IPCA-weights': folder+'Classificazione%20Ecoicop%20(4%20cifre)%20(IT1%2C168_6_DF_DCSP_IPCA3_1%2C1.0).xlsx', # folder+'Classificazione Ecoicop (4 cifre) (IT1,168_6_DF_DCSP_IPCA3_1,1.0).xlsx',
'IPCA-prices' : folder+'Classificazione%20Ecoicop%20(4%20cifre)%20(IT1%2C168_760_DF_DCSP_IPCA1B2015_1%2C1.0).xlsx', #folder+'Classificazione Ecoicop (4 cifre) (IT1,168_760_DF_DCSP_IPCA1B2015_1,1.0).xlsx',
'NIC-weights' : folder+'Classificazione%20Ecoicop%20(5%20cifre)%20(IT1%2C167_743_DF_DCSP_NIC3B2015_3%2C1.0).xlsx', # folder+'Classificazione Ecoicop (5 cifre) (IT1,167_743_DF_DCSP_NIC3B2015_3,1.0).xlsx',
'NIC-prices' : folder+'Classificazione%20Ecoicop%20(5%20cifre)%20(IT1%2C167_744_DF_DCSP_NIC1B2015_4%2C1.0).xlsx' # folder+'Classificazione Ecoicop (5 cifre) (IT1,167_744_DF_DCSP_NIC1B2015_4,1.0).xlsx'
}
Show code cell source
df_ipca = {
'prices' : pd.read_excel(filen['IPCA-prices'], sheet_name='data', decimal=',', engine='openpyxl'),
'weights': pd.read_excel(filen['IPCA-weights'], sheet_name='data', decimal=',', engine='openpyxl')
}
for kdf, idf in df_ipca.items():
idf.columns = idf.columns.str.strip() # strip whitespaces
idf = idf.set_index(['Tempo'])
idf.index = idf.index.str.strip()
idf = idf.transpose()
idf = idf.rename(columns={'index': 'Tempo'})
5.1. Inflation Indices (e.g. in Italy)#
Overall inflation is the the weighted average of inflation on different classes of goods and services, weighted for their share of expenses.
Everyone perceives its own inflation, depending on its expenses. Different indices are usually used within an economy to track inflation for some “average individual”.
Different indices may differ on values of weights, and other “details” like the effect of discounts and public transfers.
As an example, three indices are used in Italy:
NIC (Prezzi al Consumo per l’intera Collettività Nazionale), usually the general
FOI (Prezzi al Consumo per Famiglie di Operai e Impiegati), usually used for contracts, pension and inflation-linked contracts, ex-tobacco and lotteries.
IPCA (Indice Armonizzato dei Prezzi al Consumo, HIPC Harmonized Index of Consumer Prices), used for comparison and statistics in the EU
Show code cell source
# Compare IPCA, NIC and FOI
# ...
5.2. Weights and Price Indices of Classes of Goods and Services - Italy IPCA#
National and International Institutions for Statistics (in Italy, ISTAT) provide open-access databases collecting statistics about society and economics, including data about price.
ISTAT. As an example, Italian ISTAT provides data at https://esploradati.istat.it/databrowser/#/it/dw
All the data we need here is available under the category “Prezzi” - Prices. In order to reach a reasonable stability of the notebook, data have been downloaded, cleaned and stored in a folder on the repository of the project.
5.2.1. Inspect Data#
Before producing plots, price indices and weights of level-4 categories are visually inspected. Data are usually collected in tables.
5.2.1.1. Price Indices - Level-4 IPCA categories#
Show code cell source
df_ipca['prices']
| Tempo | 2018-01 | 2018-02 | 2018-03 | 2018-04 | 2018-05 | 2018-06 | 2018-07 | 2018-08 | 2018-09 | ... | 2024-09 | 2024-10 | 2024-11 | 2024-12 | 2025-01 | 2025-02 | 2025-03 | 2025-04 | 2025-05 | 2025-06 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | [00] Indice generale | 100.6 | 100.1 | 102.4 | 102.9 | 103.2 | 103.4 | 102 | 101.8 | 103.5 | ... | 123 | 123.4 | 123.3 | 123.4 | 122.4 | 122.5 | 124.4 | 124.9 | 124.8 | 125 |
| 1 | [01] -- prodotti alimentari e bevande analcoli... | 103.9 | 103 | 103.2 | 103.6 | 104.3 | 103.9 | 103.1 | 103.1 | 103 | ... | 130.8 | 132.3 | 133.3 | 132.6 | 133.8 | 133.7 | 133.8 | 134.8 | 135.4 | 135.7 |
| 2 | [011] Prodotti alimentari | 104 | 103.2 | 103.4 | 103.7 | 104.5 | 104.2 | 103.2 | 103.2 | 103.2 | ... | 131.2 | 132.9 | 133.8 | 133 | 134.1 | 134 | 134 | 134.9 | 135.5 | .. |
| 3 | [0111] Pane e cereali | 101.6 | 100.6 | 101.1 | 101.8 | 101.4 | 102 | 101.7 | 102.1 | 101.3 | ... | 127.2 | 127.6 | 127.8 | 127.8 | 128.5 | 128.2 | 128.4 | 129.2 | 129.3 | .. |
| 4 | [0112] Carni | 102.9 | 102.4 | 102.6 | 102.9 | 102.7 | 102.8 | 102.8 | 102.7 | 103 | ... | 125.6 | 125.9 | 126.7 | 127 | 127.9 | 128 | 128.7 | 129.5 | 130.2 | .. |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 145 | [SERVTRANS_5DG] Servizi relativi ai trasporti ... | 101.1 | 102.6 | 104.4 | 104.5 | 104.7 | 107 | 107.6 | 112.6 | 107 | ... | 123 | 123 | 122.8 | 124.3 | 121.8 | 121.5 | 122.9 | 127.1 | 125 | .. |
| 146 | [SERVMISC_5DG] Servizi vari (dettaglio 5-digit) | 99.8 | 100.1 | 100.3 | 100.6 | 101 | 101.1 | 101.3 | 101.3 | 101.4 | ... | 112.4 | 112.9 | 113 | 113 | 113.3 | 113.5 | 113.8 | 113.9 | 114 | .. |
| 147 | [00XEFOODUNP_5DG] Componente di fondo (core in... | 99.9 | 99.5 | 102.2 | 103 | 103.1 | 103.3 | 101.5 | 101.3 | 103.2 | ... | 117.9 | 118 | 117.8 | 117.9 | 116.1 | 116.1 | 118.2 | 119.7 | 119.9 | 120.4 |
| 148 | [00XEFOOD_5DG] Indice generale al netto dell'e... | 99.4 | 99 | 102.2 | 103 | 103.1 | 103.4 | 101.1 | 100.9 | 103.2 | ... | 115.8 | 116 | 115.6 | 115.8 | 113.5 | 113.3 | 115.9 | 117.6 | 117.7 | 118.1 |
| 149 | [00XE_5DG] Indice generale esclusi energetici ... | 100.4 | 99.9 | 102.4 | 103.2 | 103.4 | 103.6 | 101.7 | 101.5 | 103.3 | ... | 119 | 119.4 | 119.2 | 119.3 | 117.7 | 117.6 | 119.7 | 121.2 | 121.4 | 121.8 |
150 rows × 91 columns
5.2.1.2. Price Indices - Level-4 IPCA categories#
Show code cell source
df_ipca['weights']
| Tempo | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | [00] Indice generale | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 | 1000000 |
| 1 | [01] -- prodotti alimentari e bevande analcoli... | 175648 | 176326 | 175240 | 175418 | 173257 | 172583 | 205912 | 194554 | 181443 | 181801 | 181425 |
| 2 | [011] Prodotti alimentari | 162005 | 162805 | 161810 | 161903 | 159432 | 158644 | 189091 | 179008 | 166582 | 167112 | 166336 |
| 3 | [0111] Pane e cereali | 30036 | 30342 | 29853 | 29558 | 29717 | 29778 | 35767 | 33586 | 31994 | 31422 | 31513 |
| 4 | [0112] Carni | 41803 | 40944 | 40876 | 39914 | 39286 | 38162 | 45695 | 43159 | 39770 | 40253 | 40080 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 144 | [1252] Assicurazione connessa all'abitazione | .. | .. | 1102 | 456 | 469 | 398 | 525 | 466 | 470 | 517 | 487 |
| 145 | [1253] Servizi assicurativi connessi alla salu... | 467 | 584 | 530 | 560 | 605 | 720 | 903 | 781 | 728 | 748 | 692 |
| 146 | [1254] Assicurazioni sui mezzi di trasporto | 13095 | 13767 | 13191 | 12641 | 12033 | 12020 | 14649 | 12737 | 12773 | 13259 | 12486 |
| 147 | [126] Servizi finanziari n.a.c. | 13060 | 14353 | 13628 | 12614 | 15124 | 15559 | 17041 | 14343 | 14862 | 14946 | 15195 |
| 148 | [127] Altri servizi n.a.c. | 19820 | 19403 | 19046 | 19833 | 21045 | 21951 | 18066 | 20537 | 19186 | 19297 | 20422 |
149 rows × 12 columns
Show code cell source
# Useful new dataframe to match weights (yearly) and prices (monthly) later
#> Extract code and label
ddf = {}
ddf = pd.DataFrame()
ddf['code' ] = df_ipca['weights']['Tempo'].str.extract(r'(\[\d+\])')
ddf['label'] = df_ipca['weights']['Tempo'].str.strip()
#> Determine parent code
def get_parent_code(code):
if code is None or pd.isna(code):
return None
code_str = str(code).strip('[]')
if len(code_str) <= 2:
return None # no parent
elif len(code_str) == 3:
return f"[{code_str[:2]}]"
elif len(code_str) == 4:
return f"[{code_str[:3]}]"
else:
return None
#> Determine value
def get_value(code):
if code is None or pd.isna(code):
return None
code_str = str(code).strip('[]')
if len(code_str) == 4:
return int(1)
else:
return int(0)
#> Determine parents and values
ddf['parent'] = ddf['code'].map( get_parent_code )
years = [str(y) for y in range(2018, 2026)] # list of year strings
for year in years:
ddf[str(year)] = df_ipca['weights'][str(year)] / 1e4
#> Drop [00] Indice generale
ddf = ddf[ddf['code'] != '[00]']
# ddf.head(5)
5.2.2. Plots#
5.2.2.1. Level-2 IPCA Category weights#
Show code cell source
import plotly.graph_objects as go
# Create the initial figure for the first year
fig = go.Figure()
fig.add_trace(go.Sunburst(
ids=ddf['code'],
labels=ddf['code'],
parents=ddf['parent'],
values=ddf['2025'], # initial year
hoverinfo='label+value+text',
branchvalues="total", # "toatal" or "remainder"
sort=False,
text=ddf['label'],
))
fig.update_layout(title="IPCA weights - 2025")
# Create one frame per year
frames = []
for year in years:
frames.append(go.Frame(
data=[go.Sunburst(
ids=ddf['code'],
labels=ddf['code'],
parents=ddf['parent'],
values=ddf[year],
branchvalues="total", # "toatal" or "remainder"
sort=False,
text=ddf['label']
)],
name=year,
layout=go.Layout(title_text=f"IPCA weights - {year}")
))
fig.frames = frames
# Add slider steps for each year
steps = []
for year in years:
steps.append(dict(
method='animate',
args=[[year], # frame name
dict(mode='immediate',
frame=dict(duration=500, redraw=True),
transition=dict(duration=300))],
label=year
))
# Layout with slider
fig.update_layout(
# width=800, height=800,
margin=dict(t=50, l=0, r=0, b=0),
sliders=[dict(
active=years.index('2025'),
currentvalue={"prefix": "Year: "},
pad={"t": 50},
steps=steps
)],
)
fig.show()
5.2.2.2. Level-2 IPCA Category Prices#
Show code cell source
#> Classes
class_names = ddf[ ddf['parent'].isnull() ]['label'].tolist()
df_ipca['prices']['Tempo'] = df_ipca['prices']['Tempo'].str.strip()
# Add a trace for each row (now a column in df_T)
# df_classes = df['prices'][ df['prices']['Tempo'].isin(class_names) ]
df_classes = df_ipca['prices'][df_ipca['prices']['Tempo'].str.match(r'^\[\d{2}\]')].set_index('Tempo').transpose()
# Create a figure
fig = go.Figure()
df_classes
for column in df_classes.columns:
fig.add_trace(go.Scatter(
x=df_classes.index,
y=df_classes[column],
mode='lines',
name=column[:50]
))
# Customize layout
fig.update_layout(
title='IPCA price indices - 2015:100',
xaxis_title='Time',
yaxis_title='Value',
# xaxis_tickangle=-45,
hovermode='x unified',
# template='plotly_white',
height=600,
width=1000,
# legend=dict(orientation="v", x=1.02, y=1)
)
fig.show()
5.2.2.3. Level-2 IPCA Category Price Changes - Inflation#
Show code cell source
df_inflation = df_classes.pct_change(periods=12).dropna(how='any') # percent
# Create a figure
fig = go.Figure()
for column in df_inflation.columns:
fig.add_trace(go.Scatter(
x=df_inflation.index,
y=df_inflation[column],
mode='lines',
name=column[:50]
))
# Customize layout
fig.update_layout(
title='IPCA inflation by categories - 12 months price difference',
xaxis_title='Time',
yaxis_title='Value',
# xaxis_tickangle=-45,
hovermode='x unified',
# template='plotly_white',
height=600,
width=1000,
# legend=dict(orientation="v", x=1.02, y=1)
)
fig.show()
5.2.2.4. Category contributions to overall inflation#
Show code cell source
#> Need for aligning data with different time intevals
df_inflation.index = pd.to_datetime(df_inflation.index)
df_inflation.index = df_inflation.index.to_period('M')
# df_inflation
df_weights = df_ipca['weights'][ df_ipca['weights']['Tempo'].str.match(r'^\[\d{2}\]')].set_index('Tempo').transpose() / 1e6
#> Add a row 2026, just to use the same weights for all the months of 2025
row_2026 = df_weights.loc[['2025']].copy()
row_2026.index = ['2026']
df_weights = pd.concat([df_weights, row_2026],)
df_weights.index = pd.to_datetime(df_weights.index)
df_weights = df_weights.asfreq('YS')
# # df_weights_monthly = df_weights
df_weights = df_weights.resample('MS').ffill()
df_weights.index = df_weights.index.to_period('M')
# print(df_weights.index)
# print(df_inflation.index)
common_periods = df_inflation.index.intersection(df_weights.index)
df_monthly_weights = df_weights.loc[common_periods]
# df_monthly_weights
# df_inflation
df_monthly_weights.columns = df_monthly_weights.columns.str.strip()
df_inflation.columns = df_inflation.columns.str.strip()
df_contributions = df_monthly_weights * df_inflation
df_contributions = df_contributions.drop('[00] Indice generale', axis=1)
df_contributions.index = df_contributions.index.to_timestamp()
# df_contributions
Show code cell source
fig = go.Figure()
# df_contributions = df_contributions.dropna(axis=1, how='all')
for col in df_contributions.columns:
fig.add_trace(go.Scatter(
x=df_contributions.index,
y=df_contributions[col],
mode='lines',
stackgroup='one', # enables stacking
name=col
))
fig.update_layout(
title="Contributions to Year-over-Year Inflation (Weighted by IPCA)",
xaxis_title="Date",
yaxis_title="Contribution to Inflation (%)",
# width=1000,
# height=600
)
fig.show()
Show code cell source
# df_plot = df_contributions.reset_index()
df_plot = df_contributions.reset_index().rename(columns={'Tempo': 'Time'})
# df_plot['Tempo'] = pd.to_datetime(df_plot['Tempo'])
df_plot.index = pd.to_datetime(df_plot.index)
df_plot
# print(df_plot.columns.to_list())
# # print(df_plot.head())
# print(df_plot.dtypes)
# Create a new DataFrame by resetting the index and renaming the index column to 'Time'
df_new = df_contributions.reset_index()
# If the old index had no name, the column will be called 'index', so rename it:
if 'index' in df_new.columns:
df_new = df_new.rename(columns={'index': 'Time'})
else:
# If it has a name, replace that with 'Time'
old_name = df_contributions.index.name
if old_name is not None and old_name in df_new.columns:
df_new = df_new.rename(columns={old_name: 'Time'})
else:
# If the index has no name and no 'index' column, just add one:
df_new['Time'] = df_contributions.index
# Now df_new has a clean 'Time' column and a fresh integer index
# print(df_new.head())
df_new
| Tempo | Time | [01] -- prodotti alimentari e bevande analcoliche | [02] -- bevande alcoliche e tabacchi | [03] -- abbigliamento e calzature | [04] -- abitazione, acqua, elettricità, gas e altri combustibili | [05] -- mobili, articoli e servizi per la casa | [06] -- servizi sanitari e spese per la salute | [07] -- trasporti | [08] -- comunicazioni | [09] -- ricreazione, spettacoli e cultura | [10] -- istruzione | [11] -- servizi ricettivi e di ristorazione | [12] -- altri beni e servizi |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2019-01-01 | 0.000834 | 0.000754 | -0.000187 | 0.004464 | 0.0 | 0.000341 | 0.001504 | -0.001614 | -0.000238 | 0.0 | 0.001366 | 0.001651 |
| 1 | 2019-02-01 | 0.003028 | 0.001132 | -0.000199 | 0.00446 | 0.000154 | 0.00034 | 0.000895 | -0.001936 | -0.000178 | 0.0 | 0.001114 | 0.001749 |
| 2 | 2019-03-01 | 0.001679 | 0.001058 | 0.00023 | 0.00446 | -0.000076 | 0.000255 | 0.001778 | -0.001873 | -0.00012 | 0.000013 | 0.001106 | 0.001636 |
| 3 | 2019-04-01 | 0.000334 | 0.000772 | 0.000148 | 0.003974 | -0.000076 | 0.000297 | 0.00399 | -0.002419 | -0.00012 | 0.000013 | 0.001935 | 0.001916 |
| 4 | 2019-05-01 | 0.000664 | 0.000676 | 0.000222 | 0.003861 | 0.0 | 0.000297 | 0.002936 | -0.002491 | -0.00006 | 0.000013 | 0.00132 | 0.00153 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 73 | 2025-02-01 | 0.004449 | 0.00079 | -0.000433 | 0.003935 | 0.000513 | 0.001076 | -0.00013 | -0.001019 | 0.001017 | 0.000292 | 0.003506 | 0.002718 |
| 74 | 2025-03-01 | 0.00473 | 0.001011 | 0.000905 | 0.007548 | 0.000448 | 0.001188 | -0.00142 | -0.000944 | 0.001073 | 0.000292 | 0.004101 | 0.002699 |
| 75 | 2025-04-01 | 0.005978 | 0.000567 | 0.000458 | 0.005752 | 0.000318 | 0.001146 | -0.001411 | -0.000976 | 0.00075 | 0.000292 | 0.00492 | 0.002683 |
| 76 | 2025-05-01 | 0.005951 | 0.000621 | 0.000516 | 0.004934 | 0.000446 | 0.001183 | -0.003094 | -0.000877 | 0.00053 | 0.000292 | 0.004356 | 0.002681 |
| 77 | 2025-06-01 | 0.006797 | 0.000702 | 0.000458 | 0.002547 | 0.000509 | 0.001257 | -0.001422 | -0.000852 | 0.000584 | 0.000292 | 0.004312 | 0.002762 |
78 rows × 13 columns
Show code cell source
y_cols = [col for col in df_new.columns if col != 'Time']
df_long = df_new.melt(id_vars='Time', value_vars=y_cols, var_name='Category', value_name='Value')
fig = px.bar(
df_long,
x='Time', # 'x',
y='Value',
color='Category',
title='Stacked bars with relative mode'
)
fig.update_layout(barmode='stack') # relative')
fig.show()
# df_contributions
5.3. Correlations in macroeconomics with inflation#
Some correlations exist1 between inflation and other macroeconocmics quantitites.
Phillips Curve: inverse relation between inflation and unemployment (in the short-run)
Money supply in the long-run “Inflation is a monetary phenomenon”, M.Friedman.
5.4. Control of Inflation#
Control of inflation is one of the goals of central banks, like the FED and the ECB.
Central banks aims at controlling inflation, matching target inflation (usaully set as 2%) by means of monetary policy:
interest rates (cost of money)
non-conventional actions, like quantitative easing (QE)/tightening (QT)
A goverment may indirectly influence inflation with fiscal policy, as taxation and government spending can influence demand.
Credibility of targets, and actors through their actions and forward guidance may influence inflation as well: expectations influences inflation.
- 1
…